package wsi.detect;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.rygh.core.util.timer.TaskTime;
import org.rygh.core.util.timer.TaskTimeManager;
import org.rygh.semantic.gui.swing.input.Input;

import wsi.dataguide.tree.WSXmlParser;
import wsi.dataguide.tree.WSXmlTree;
import wsi.function.Function;
import wsi.input.InputCandidate;
import wsi.input.feedback.FeedbackTuple;
import wsi.match.SubtreeMatch;
import wsi.subgraph.tree.EntityTree;

public class InputDetector {
	static boolean __DEBUG = false;

	private final Map<Input, InputCandidate> waitingList = new HashMap<>();
	private final Set<Input> inputCache = new HashSet<>();
	private final List<Input> inputs = new ArrayList<>();

	private final DetectHelper detectHelper = new DetectHelper();
	private final TaskTimeManager timeManager = new TaskTimeManager();

	private final Function function;

	private int webcalls = 0;
	private int expectedSize = 0;

	public InputDetector(Function function) {
		this.function = function;
	}

	public void addInput(Input input) {
		/*
		 * cannot use inputCache here to prune, it is added this value at the
		 * beginning of detection.
		 */

		if (inputs.contains(input))
			return;

		inputs.add(input);
	}

	private boolean checkRules(String value) {
		if (value.length() > 50 || value.startsWith("http://"))
			return false;

		return true;
	}

	public void clear() {
		webcalls = 0;
		inputs.clear();
		inputCache.clear();
		waitingList.clear();
	}

	public boolean detect(Input input) {
		return detect(input, -1, null);
	}

	public boolean detect(Input input, int degree, FeedbackTuple tuple) {
		if (input == null || isEnough() || inputCache.contains(input))
			return false;

		inputCache.add(input);

		TaskTime timer = timeManager.newTask(input.getEntity().getValue());
		detectHelper.setTimer(timer);

		/*
		 * checking entity tree takes longer time than checking web call (we
		 * have cache), thus we check web call first.
		 */

		webcalls++;

		String val = input.getValue();

		if (!checkRules(val))
			return false;

		if (__DEBUG) {
			// System.out.println("begin to call web " + input);
		}

		String xml = detectHelper.prepareHtml(input, function);

		if (__DEBUG) {
			// System.out.println("end calling web " + input);
		}

		if (xml == null || xml.trim().isEmpty()) {

			// if (function.getName().equals("getReleaseById")) {
			// System.out.println("xml empty.");
			// }

			return false;
		}

		//

		if (__DEBUG) {
			// System.out.println("begin to extract subgraph " + input);
		}

		EntityTree etree = detectHelper.prepareTree(input, function);

		if (__DEBUG) {
			// System.out.println("end extracting subgraph " + input);
		}

		if (etree == null) {
			// if (function.getName().equals("getReleaseById")) {
			// System.out.println("entity tree empty.");
			// }

			return false;
		}

		if (__DEBUG) {
			System.out.println("begin to match " + input);
		}

		SubtreeMatch sm = detectHelper.detect(input, etree, xml);

		if (__DEBUG) {
			System.out.println("end matching " + input);
		}

		if (sm == null || sm.isEmpty()) {

			// if (function.getName().equals("getReleaseById")) {
			// System.out.println("subtree match empty: "
			// + input.getEntity().getValue());
			// }

			return false;
		}

		if (__DEBUG) {
			// System.out.println("begin to count unique " + input);
		}

		int dm = sm.getNumberOfDistinctMatch();

		if (__DEBUG) {
			// System.out.println("end counting unique " + input);
		}

		boolean contains;

		/*
		 * we do not require this as of 13-11-12
		 */
		// contains = sm.containsMatch(input.getValue());
		contains = true;

		System.out
				.printf("[info] matched (%b) count %d / %d, degree %d, input size %d / %d\n",
						contains, dm, sm.size(), degree, inputs.size(),
						expectedSize);

		if (!contains)
			return false;

		//

		if (tuple != null) {
			tuple.setSubtreeMatch(sm);
			tuple.setEntityTree(etree);
		}

		if (dm > 1) {
			addInput(input);

			return true;
		} else {
			assert dm == 1;

			String value = sm.getMatchPairs().get(0).getXmlLeaf().getValue()
					.getValue();

			return waitlist(input, xml, value);
		}
	}

	public DetectHelper getDetectHelper() {
		return detectHelper;
	}

	public int getExpectedSize() {
		return expectedSize;
	}

	public Input[] getInputArray() {
		return inputs.toArray(new Input[inputSize()]);
	}

	public List<Input> getInputs() {
		return inputs;
	}

	public TaskTimeManager getTimeManager() {
		return timeManager;
	}

	public int getWebcalls() {
		return webcalls;
	}

	public int inputSize() {
		return inputs.size();
	}

	public boolean isEnough() {
		return expectedSize > 0 && inputs.size() >= expectedSize;
	}

	public boolean isInputEmpty() {
		return inputs.isEmpty();
	}

	public void setExpectedSize(int expectedSize) {
		this.expectedSize = expectedSize;
	}

	private boolean waitlist(Input input, String xml, String value) {
		assert !waitingList.containsKey(input);

		WSXmlParser parser = new WSXmlParser(input, xml, true);
		WSXmlTree tree = parser.parse();
		assert parser.isValid();

		tree.replaceValue(value, "");

		boolean empty = waitingList.isEmpty();
		final InputCandidate cand = new InputCandidate(input,
				tree.toXmlString());

		// System.out.println("new xml " + cand.getXml());

		waitingList.put(input, cand);

		if (empty) {
			System.out.printf("\t first value %s\n", input.getValue());

			return false;
		}

		/*
		 * if tree_xml equals to any previous tree_xml, then it is invalid.
		 * (last.fm)
		 */

		boolean has_equal = false;

		for (InputCandidate ic : waitingList.values()) {
			if (ic.getInput() == input)
				continue;

			if (cand.xmlEqualsTo(ic)) {
				has_equal = true;

				cand.setValid(false);
				ic.setValid(false);

				if (inputs.remove(ic.getInput()))
					System.out.printf("\t remove candidate value %s\n",
							ic.getInput());
			}
		}

		if (has_equal) {
			System.out.printf("\t invalid candidate value %s\n",
					input.getValue());

			return false;
		}

		/*
		 * if there is no equal tree_xml, then inputs in waiting lists which are
		 * not invalid are valid, we add them. we have a cache to prune
		 * duplicated values.
		 */

		for (Entry<Input, InputCandidate> e : waitingList.entrySet()) {
			InputCandidate v = e.getValue();
			Input i = v.getInput();

			if (v.isUnknown()) {
				System.out.printf("\t add candidate value %s\n", i.getValue());

				v.setValid(true);

				addInput(i);
			}
		}

		return true;
	}
}
